package cn.zhaopin.starter.security;

import cn.zhaopin.starter.security.cache.SecurityCacheUtil;
import cn.zhaopin.starter.security.common.SecurityConstant;
import cn.zhaopin.starter.security.convert.PhoneAndSmsAuthenticationConverter;
import cn.zhaopin.starter.security.convert.WechatMpAuthenticationConverter;
import cn.zhaopin.starter.security.convert.WechatMpBindPhoneAuthenticationConverter;
import cn.zhaopin.starter.security.convert.WeworkThirdAuthenticationConverter;
import cn.zhaopin.starter.security.filter.ApiTokenAfterAuthorizationWebFilter;
import cn.zhaopin.starter.security.filter.CorsFilter;
import cn.zhaopin.starter.security.filter.TraceFilter;
import cn.zhaopin.starter.security.handler.*;
import cn.zhaopin.starter.security.manager.*;
import cn.zhaopin.starter.security.point.BasicHttpBasicServerAuthenticationEntryPoint;
import cn.zhaopin.starter.security.properties.JwtProperties;
import cn.zhaopin.starter.security.properties.SecurityProperties;
import cn.zhaopin.starter.security.service.SecurityUserDetailService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;

import javax.annotation.Resource;

/**
 * Description: 认证授权安全配置
 *
 * @author zuomin (myleszelic@outlook.com)
 * @date: 2021/09/28-13:25
 */
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {

    private final String[] API_NO_INTERCEPT = {"/actuator/**"};

    private final String[] STATIC_RESOURCE = {"/*.html","/*.txt","/*.txt", "/favicon.ico", "/*.html", "/*.js", "/*.css"};

    private final String[] SWAGGER_RESOURCE = {
            "/webjars/**",
            "/doc.html",
            "/pic/v2/api-docs",
            "/h5/v2/api-docs",
            "/applet/v2/api-docs",
            "/swagger-resources/**"
    };

    @Value("${app.security.whitelist}")
    private String[] excludeAuthPaths;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Resource
    private SecurityProperties securityProperties;

    @Resource
    private SecurityCacheUtil securityCacheUtil;

    @Resource
    private SecurityUserDetailService securityUserDetailService;

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {


        http
                .cors().and()
                .csrf(ServerHttpSecurity.CsrfSpec::disable)
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
                .exceptionHandling()
                // 用来解决匿名用户访问无权限资源时的异常
                .authenticationEntryPoint(new BasicHttpBasicServerAuthenticationEntryPoint())
                // 用来解决认证过的用户访问无权限资源时的异常
                .accessDeniedHandler(new BasicServerAccessDeniedHandler())
                .and()
                .authorizeExchange()
                // whitelist 放行
                .pathMatchers(API_NO_INTERCEPT).permitAll()
                .pathMatchers(STATIC_RESOURCE).permitAll()
                .pathMatchers(SWAGGER_RESOURCE).permitAll()
                .pathMatchers(excludeAuthPaths).permitAll()
                // 预检options 放行
                .pathMatchers(HttpMethod.OPTIONS).permitAll()
                .anyExchange().access(new ApiTokenReactiveAuthorizationManager(securityCacheUtil, securityProperties.getJwt()))
                .and()
                .securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
                .addFilterAt(new TraceFilter(), SecurityWebFiltersOrder.FIRST)
                .addFilterAt(weWorkThirdAuthenticationWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
                .addFilterAt(phoneAndSmsAuthenticationWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
                .addFilterAt(wechatMpAuthenticationWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
                .addFilterAt(wechatMpBindPhoneAuthenticationWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
                .addFilterAt(new CorsFilter(), SecurityWebFiltersOrder.CORS)
                .addFilterAfter(new ApiTokenAfterAuthorizationWebFilter(securityCacheUtil, securityProperties.getJwt()), SecurityWebFiltersOrder.AUTHORIZATION)
                .logout()
                .requiresLogout(ServerWebExchangeMatchers.pathMatchers(SecurityConstant.PATH.LOGOUT))
                .logoutHandler(new BasicServerLogoutHandler(securityCacheUtil, securityProperties.getJwt()))
                .logoutSuccessHandler(new BasicServerLogoutSuccessHandler(securityCacheUtil))
        ;

        return http.build();


    }

    /**
     * 企业微信授权登陆
     * @return
     */
    private AuthenticationWebFilter weWorkThirdAuthenticationWebFilter() {
        final AuthenticationWebFilter webFilter = new AuthenticationWebFilter(new WeWorkInsideAuthenticationManager(securityUserDetailService));

        webFilter.setRequiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, SecurityConstant.PATH.LOGIN_WEWORK_THIRD_AUTH_CODE));
        webFilter.setServerAuthenticationConverter(new WeworkThirdAuthenticationConverter());
        webFilter.setAuthenticationSuccessHandler(new BasicServerAuthenticationSuccessHandler(securityProperties.getJwt(), securityCacheUtil));
        webFilter.setAuthenticationFailureHandler(new BasicAuthenticationFailureHandler());

        return webFilter;
    }

    /**
     * 手机号验证码
     * @return
     */
    private AuthenticationWebFilter phoneAndSmsAuthenticationWebFilter() {
        final AuthenticationWebFilter webFilter = new AuthenticationWebFilter(new PhoneAndSmsAuthenticationManager(securityUserDetailService, securityProperties, securityCacheUtil));

        webFilter.setRequiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, SecurityConstant.PATH.LOGIN_PHONE_NUM_SMS));
        webFilter.setServerAuthenticationConverter(new PhoneAndSmsAuthenticationConverter());
        webFilter.setAuthenticationSuccessHandler(new BasicServerAuthenticationSuccessHandler(securityProperties.getJwt(), securityCacheUtil));
        webFilter.setAuthenticationFailureHandler(new BasicAuthenticationFailureHandler());

        return webFilter;
    }

    /**
     * 微信小程序授权登陆
     * @return
     */
    private AuthenticationWebFilter wechatMpAuthenticationWebFilter() {
        final AuthenticationWebFilter webFilter = new AuthenticationWebFilter(new WechatMpAuthenticationManager(securityUserDetailService));

        webFilter.setRequiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, SecurityConstant.PATH.LOGIN_WECHAT_MP_CODE));
        webFilter.setServerAuthenticationConverter(new WechatMpAuthenticationConverter());
        webFilter.setAuthenticationSuccessHandler(new BasicServerAuthenticationSuccessHandler(securityProperties.getJwt(), securityCacheUtil));
        webFilter.setAuthenticationFailureHandler(new BasicAuthenticationFailureHandler());

        return webFilter;
    }

    /**
     * 微信小程序绑定手机号授权登陆
     * @return
     */
    private AuthenticationWebFilter wechatMpBindPhoneAuthenticationWebFilter() {
        final AuthenticationWebFilter webFilter = new AuthenticationWebFilter(new WechatMpBindPhoneAuthenticationManager(securityUserDetailService));

        webFilter.setRequiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, SecurityConstant.PATH.LOGIN_WECHAT_MP_BIND_PHONE));
        webFilter.setServerAuthenticationConverter(new WechatMpBindPhoneAuthenticationConverter());
        webFilter.setAuthenticationSuccessHandler(new BasicServerAuthenticationSuccessHandler(securityProperties.getJwt(), securityCacheUtil));
        webFilter.setAuthenticationFailureHandler(new BasicAuthenticationFailureHandler());

        return webFilter;
    }


}
